"""
Code to store knowledge about 'things', i.e. 'Num' or 'Sym' (for
numbers and symbols, respectively).

The main magic here is the 'keep' function that accepts a new
thing and updates our knowledge of this thing.

After 'keeping' many 'things', these classes become the working 
memory of data seen in a particular context.

This code is part of KNIFE: cropping complexity, culling confusion, 
and cutting the crap since 2012
   
           .----------------._________________
           |=()=========---  \-----------     `\ 
      jgs  \_________________/^^^^^^^^^^^^^^^^^^

(c) 2012, Tim Menzies, tim@menzies.us, LGPL v3.0
"""

from lib import *
from dict import *

class Num(object):
  def __init__(self):
    """
    'Max' and 'min' are set to impossibe values so
    that the first time we 'keep' anything, they get
    reset. 'Sum' and 'sumSq' are used to compute std. 
    deviation
    """
    self.max= -10**10; self.min= -1*self.max
    self.sum= self.sumSq= self.n= 0

  def keep(self,n) :
    """
    Update our summary of the 'n' numbers seen so far.
    Returns 'n' coerced to its right type (if needed).
    """
    n           = float(n)
    self.n     += 1
    self.sum   += n
    self.sumSq += n*n
    self.max    = max(self.max,n)
    self.min    = min(self.min,n)
    return n

  def expected(self) :
    "Returns the mean of this sample."
    return self.mean()

  def wiggle(self) :
    "Returns the standard deviation of this sample."
    return self.stdev()

  def norm(self,n) :
    "Normalizes a number 'n' to 0..1, for 'min' to 'max'."
    return (n - self.min)/(self.max - self.min)

  def mean(self) :
    "Returns the mean of the sample."
    return self.sum/self.n

  def stdev(self) : 
    "Returns the standard deviation of the sample."
    return stdev(self.n,self.sum,self.sumSq)

  def __repr__(self) : 
    "Prety printing."
    return s(self.__dict__)

class Sym(object):
  def __init__(self) :
    """
    The counts of what symbols seen so far is stored in 'counts'.
    The most frequent symbol is the 'mode', which has been seen 
    'most' number of times.
    """
    self.counts=Dict()
    self.most = self.n = 0
    self.mode = None

  def expected(self):
    "Returns the mode of this sample."
    return self.mode

  def wiggle(self):
    "Returns the entropy of this sample."
    return self.entropy()

  def keep(self,n) : 
    """
    Updates our summary of the 'n' symbols seen so far.
    Returns 'n'.
    """
    self.n += 1
    count = self.counts.inc(n)
    if count > self.most:
      self.most = count
      self.mode = n
    return n

  def entropy(self):
    "Returns the entropy of the sample."
    sum  = p = 0
    for k in self.counts:
      v = self.counts[k]*1.0
      p += v/self.n
      sum += -1*p*math.log(max(p,0.00001),2)
    return sum

  def __repr__(self) :
    "Pretty print."
    return s(self.counts.items())
